#!/usr/bin/env python
# encoding: utf-8
"""
@author:zwb
@file: mixin.py
@contact: zwb15083976291@163.com
@time: 2021/10/10
"""
from collections import OrderedDict
from collections.abc import Mapping
from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework.fields import empty, SkipField
from rest_framework import serializers

from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.relations import PKOnlyObject
from rest_framework.settings import api_settings
from rest_framework.fields import get_error_detail, set_value


class SpecifySerializeFieldsMixin(object):
    """可以动态指定序列化、反序列化字段的拓展类,绕开默认只能序列化可读字段"""
    constraint_names = ['required', 'allow_blank', 'allow_null', 'default', 'allow_empty']

    def __init__(self, instance=None, data=empty, serial_fields: "iterable obj" = [],
                 deserial_fields: dict = {}, context: dict = {}, **kwargs):
        super().__init__(instance, data, context=context, **kwargs)
        self.serial_fields = serial_fields  # 该参数用户动态指定用于序列化的字段
        self.deserial_fields = deserial_fields  # 该参数用户动态指定用于反序列化的字段

    @property
    def _readable_fields(self):
        for field in self.fields.values():
            if self.serial_fields:
                if field.field_name in self.serial_fields:
                    yield field
            else:
                if not field.write_only:
                    yield field

    @property
    def _writable_fields(self):
        for field in self.fields.values():
            if self.deserial_fields:
                if field.field_name in self.deserial_fields:
                    constraint = self.deserial_fields.get(field.field_name)
                    for key, value in constraint.items():
                        assert key in self.constraint_names, f'{key} 不是自定义序列化器规定的字段校验条件'
                        setattr(field, key, value)
                        field.error_messages['null'] = '该字段不能为空'
                    yield field
            else:
                if not field.read_only:
                    yield field

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        对deserial_fields中设置为required=True,allow_null=False的字段强制进行类型检查和验证方法验证
        对设置为required=False,allow_null=True的字段如果原始值为None直接略过类型检查和验证方法验证
        """
        if not isinstance(data, Mapping):
            message = self.error_messages['invalid'].format(
                datatype=type(data).__name__
            )
            raise ValidationError({
                api_settings.NON_FIELD_ERRORS_KEY: [message]
            }, code='invalid')

        ret = OrderedDict()
        errors = OrderedDict()
        fields = self._writable_fields

        for field in fields:
            validate_method = getattr(self, 'validate_' + field.field_name, None)
            primitive_value = field.get_value(data)
            try:
                if (primitive_value in ([], '', {})) and field.allow_null:
                    raise BlankField()
                validated_value = field.run_validation(primitive_value)
                if validated_value is None and field.allow_null:  # 如果是None,同时设置allow_null=True,直接忽略
                    raise SkipField()
                if validate_method is not None:
                    validated_value = validate_method(validated_value)
            except ValidationError as exc:
                errors[field.field_name] = exc.detail
            except DjangoValidationError as exc:
                errors[field.field_name] = get_error_detail(exc)
            except SkipField:
                pass
            except BlankField:
                if isinstance(field, serializers.CharField):
                    validated_value = ''
                elif isinstance(field, serializers.ListField):
                    validated_value = []
                # elif isinstance(field, (serializers.IntegerField, serializers.FloatField, serializers.DecimalField)):
                #     validated_value = None
                elif isinstance(field, serializers.Serializer):
                    validated_value = {}
                else:
                    validated_value = None
                set_value(ret, field.source_attrs, validated_value)
            else:  # 字符串或者列表字段，如果参数值是del，直接设置为空字符串
                if validated_value == 'del' and isinstance(field, (serializers.CharField, serializers.ListField)):
                    validated_value = ''

                set_value(ret, field.source_attrs, validated_value)

        if errors:
            raise ValidationError(errors)

        return ret

    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            # We skip `to_representation` for `None` values so that fields do
            # not have to explicitly deal with that case.
            #
            # For related fields with `use_pk_only_optimization` we need to
            # resolve the pk value.
            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                ret[field.field_name] = field.to_representation(attribute)

        return ret


class BlankField(Exception):
    pass
